/*!
 * tigua 0.0.1 - JavaScript Canvas Library
 *
 * Copyright (c) 2010 David M. Lopez (dML)
 * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
 */
tigua = (function () {
    var idCount=1,
    toString = Object.prototype.toString,
    math = Math,
    pow = math.pow,
    rad2deg = 180/math.PI,
    deg2rad = math.PI/180,
    T = {};
     
    //
    //private utility functions
    //
    function apply(o, c, defaults) {
        if(defaults){
            apply(o, defaults);
        }
        if(o && c && typeof c == 'object'){
            for(var p in c){
                o[p] = c[p];
            }
        }
        return o;
    }

    function isEmpty(v, allowBlank) {
        return v === null || v === undefined || ((isArray(v) && !v.length)) || (!allowBlank ? v === '' : false);
    }
        
    function isArray(v) {
        return toString.apply(v) === '[object Array]';
    }
        
    function isDate(v) {
        return toString.apply(v) === '[object Date]';
    }
     
    function isObject(v) {
        return !!v && toString.call(v) === '[object Object]';
    }
     
    function isPrimitive(v) {
        return isString(v) || isNumber(v) || isBoolean(v);
    }
     
    function isFunction(v) {
        return toString.apply(v) === '[object Function]';
    }
     
    function isNumber(v) {
        return typeof v === 'number' && isFinite(v);
    }
     
    function isString(v) {
        return typeof v === 'string';
    }
     
    function isBoolean(v) {
        return typeof v === 'boolean';
    }
     
    function isDefined(v) {
        return typeof v !== 'undefined';
    }

    //
    //
    //Observable: Interface for event listeners
    //
    //
    T.Observable = function() {
        this._events={};
    }
    T.Observable.prototype.addEventListener = function(type,handler) {
        var h = this._events[type];
        if (h) {
            var oldh = h;
            var newh = function(e) {
                oldh(e);
                handler(e);
            }
            this._events[type] = newh;
        } else {
            this._events[type] = handler;
        }
    }
    T.Observable.prototype.fireEvent = function(event) {
        var h = this._events[event.type];
        if (h) {
            h.call(this,event);
        }
    }
    T.Observable.prototype.hasEventListener = function(type) {
        var h = this._events[type];
        return isDefined(h);
    }

    //
    //
    //Canvas: wrapper around HTML canvas tag
    //
    //
    //Couple of ways to construct this object
    //id is a string id of a canvas element in the page
    //id is an object with id,x,y,width,height attributes possible
    //if id is not provided at all, then a canvas element will be created and added to the body
    T.canvas = function(id) {
        var config = id;
        if (isObject(config)) {
            id = config.id;
        }
       
        if (isString(id)) {
            this.cnvs = document.getElementById(id);
        } else {
            var c = document.createElement('canvas');
            c.id = "tigua-canvas-"+idCount++;
            c.style.position='absolute';
            document.body.appendChild(c);
            this.cnvs = c;
        }
        this.ctx = this.cnvs.getContext('2d');
       
        if (isObject(config)) {
            if (isNumber(config.x)) {
                this.setX(config.x);
            }
            if (isNumber(config.y)) {
                this.setY(config.y);
            }
            if (isNumber(config.height)) {
                this.setHeight(config.height);
            }
            if (isNumber(config.width)) {
                this.setWidth(config.width);
            }
        }

        var that=this;
        this.cnvs.addEventListener('click',function(e){
            that.handleEvent(e);
        },false);
        this.cnvs.addEventListener('mousemove',function(e){
            that.handleMouseMoveEvent(e);
        },false);
        this.cnvs.addEventListener('mousedown',function(e){
            that.handleEvent(e);
        },false);
        this.cnvs.addEventListener('mouseup',function(e){
            that.handleEvent(e);
        },false);

        this.isAnimating = false;
        this.isTweening   = false;

        this._comps = {};
        this._add_comps = [];
        this._del_comps = [];
        this._tween_tsks = [];
    }

    T.canvas.prototype.handleEvent = function(event) {
        var x = event.clientX - this.cnvs.offsetLeft;
        var y = event.clientY - this.cnvs.offsetTop;
        var type = event.type;
        var dirty = false;

        //create shallow clone of event and tweak x and y
        var cevent = {
            altKey: event.altKey,
            bubbles: event.bubbles,
            button: event.button,
            cancelBubble: event.cancelBubble,
            cancelable: event.cancelable,
            canvasX: event.clientX,
            canvasY: event.clientY,
            clientX: x,
            clientY: y,
            ctrlKey: event.ctrlKey,
            isChar: event.isChar,
            metaKey: event.metaKey,
            screenX: event.screenX,
            screenY: event.screenY,
            shiftKey: event.shiftKey,
            target: event.target,
            timeStamp: event.timeStamp,
            type: event.type
        };

        for (var c in this._comps) {
            var comp = this._comps[c];
            if (comp.hasEventListener(type) && comp.pointInside(x,y)) {
                cevent.target=comp;
                comp.fireEvent(cevent);
                dirty=true;
            }
        }

        if (dirty && !this.isAnimating) this.render();
    }

    T.canvas.prototype.handleMouseMoveEvent = function(event) {
        var x = event.clientX - this.cnvs.offsetLeft;
        var y = event.clientY - this.cnvs.offsetTop;
        var type = event.type;
        var dirty = false;

        //create shallow clone of event and tweak x and y
        var cevent = {
            altKey: event.altKey,
            bubbles: event.bubbles,
            button: event.button,
            cancelBubble: event.cancelBubble,
            cancelable: event.cancelable,
            canvasX: event.clientX,
            canvasY: event.clientY,
            clientX: x,
            clientY: y,
            ctrlKey: event.ctrlKey,
            isChar: event.isChar,
            metaKey: event.metaKey,
            screenX: event.screenX,
            screenY: event.screenY,
            shiftKey: event.shiftKey,
            target: event.target,
            timeStamp: event.timeStamp,
            type: event.type
        };

        for (var c in this._comps) {

            var comp = this._comps[c];

            if (comp.pointInside(x,y)) {
                cevent.target=comp;
                if (comp.hasEventListener(type)) {
                    comp.fireEvent(cevent);
                    dirty=true;
                }

                if (!comp._inside) {
                    comp._inside=true;
                    if (comp.hasEventListener('mouseover')) {
                        cevent.type='mouseover';
                        comp.fireEvent(cevent);
                        dirty=true;
                    }
                }
            } else if (comp._inside) {
                comp._inside=false;
                if (comp.hasEventListener('mouseout')) {
                    cevent.type='mouseout';
                    comp.fireEvent(cevent);
                    dirty=true;
                }
            }
        }

        if (dirty && !this.isAnimating) this.render();
    }

    T.canvas.prototype.setWidth = function(width) {
        if (isNumber(width)) {
            this.cnvs.width = width;
        }
    }
     
    T.canvas.prototype.setHeight = function(height) {
        if (isNumber(height)) {
            this.cnvs.height = height;
        }
    }

    T.canvas.prototype.setDimension = function(width,height) {
        this.setWidth(width);
        this.setHeight(height);
    }
     
    T.canvas.prototype.setX = function(x) {
        if (isNumber(x)) {
            this.cnvs.offsetLeft = x;
        }
    }
     
    T.canvas.prototype.setY = function(y) {
        if (isNumber(y)) {
            this.cnvs.offsetTop = y;
        }
    }
     
    T.canvas.prototype.setXY = function(x,y) {
        this.setX(x);
        this.setY(y);
    }
     
    T.canvas.prototype.getId = function() {
        return this.cnvs.id;
    }
     
    T.canvas.prototype.getX = function() {
        return this.cnvs.offsetLeft;
    }
     
    T.canvas.prototype.getY = function() {
        return this.cnvs.offsetTop;
    }
     
    T.canvas.prototype.getWidth = function() {
        return this.cnvs.width;
    }
     
    T.canvas.prototype.getHeight = function() {
        return this.cnvs.height;
    }

    T.canvas.prototype.setBackground = function(comp) {
        if (isString(comp)) {
            this.ctx.fillStyle = comp;
        } else if (isObject(comp)) {
            comp.setXY(0,0);
            comp.setDimension(this.getWidth(),this.getHeight());
        }
       
        this.background = comp;
    }
     
    T.canvas.prototype.addComponent = function(comp) {
        if (this.isAnimating) {
            this._add_comps.push(comp);
        } else {
            this._comps[comp.id]=comp;
            comp.parent = this;
        }
    }
     
    T.canvas.prototype.removeComponent = function(comp) {
        if (this.isAnimating) {
            this._del_comps.push(comp);
        } else {
            delete comp.parent;
            delete this._comps[comp.id];
        }
    }

    T.canvas.prototype.componentCount = function() {
        var count=0;
        for (var c in this._comps) {
            count++;
        }
        return count;
    }
     
    T.canvas.prototype.render = function() {
        var comps      = this._comps;
        var ctx        = this.ctx;
        var background = this.background;
       
        if (background) {
            if (isString(background)) ctx.fillRect(0,0,this.cnvs.width,this.cnvs.height);
            else background.doRender(ctx,this);
        }
       
        for (var c in comps) {
            comps[c].doRender(ctx,this);
        }
       
        var ac    = this._add_comps;
        var acl   = ac.length;
        var dc    = this._del_comps;
        var dcl   = dc.length;
       
        for (var i=0;i<acl;i+=1) {
            var comp = ac[i];
            comps[comp.id]=comp;
            comp.parent = this;
        }
       
        for (var i=0;i<dcl;i+=1) {
            delete comp.parent;
            delete comps[dc[i].id];
        }
       
        this._add_comps=[];
        this._del_comps=[];
    }
     
    T.canvas.prototype.startAnimation = function(delay) {
        delay = delay || 10;
        if (this.isAnimating) return;

        var that = this;
        function doit() {
            that.render();
            if (that.isAnimating) setTimeout(doit,delay);
        }
        //this._intervalId = setInterval(doit, delay);
        this.isAnimating=true;
        setTimeout(doit,delay);
    }
     
    T.canvas.prototype.stopAnimation = function() {
        this.isAnimating=false;
        //if (this._intervalId) {
        //    clearInterval(this._intervalId);
        //    delete this._intervalId;
        //    this.isAnimating=false;
        //}
    }

    function createTweenTask(comp,attr,to,ms,easing,callback) {
        easing = easing || 'linear';
        easing = T.easing_formulas[easing];
        easing = easing || T.easing_formulas.linear;

        var ao      = {};
        ao.from     = comp[attr];
        ao.to       = to;
        ao.ms       = ms;
        ao.comp     = comp;
        ao.attr     = attr;
        ao.size     = ao.to - ao.from;
        ao.diff     = ao.size/ao.ms;
        ao.start    = +new Date;
        ao.easing   = easing;
        ao.callback = callback;
      
        return ao;
    }
            
    T.canvas.prototype.addTweenTask = function(task) {
      this._tween_tsks.push(task);
    }
    
    T.canvas.prototype.doTweenTasks = function() {
        var tasks = this._tween_tsks;
        var l = tasks.length-1;

        for (var i=l;i>=0;i--) {
            var task = tasks[i];
            var now = +new Date;
            var duration = now - task.start;
            var percent_complete = duration/task.ms;
            if (percent_complete > 1) percent_complete=1;
            task.comp[task.attr] = task.from + (task.size * task.easing(percent_complete));
            if (percent_complete >= 1) {
                tasks.splice(i,1);
                task.comp[task.attr] = task.to;
                task.callback && task.callback(task.comp,task.attr,task);
            }
        }
        if (tasks.length == 0) {
            this.isTweening = false;
        }
    }
    
    T.canvas.prototype.startTweenTasks = function() {
        if (this.isTweening == false) {
            var that = this;
            function doit() {
                that.doTweenTasks();
                if (!that.isAnimating) that.render();
                if (that.isTweening) setTimeout(doit);
            }
            this.isTweening = true;
            doit();
        }
    }

    //
    //
    //component: base class for all objects that are displayed
    //           by a canvas element
    //
    //Responds to the following events:
    //    click,mousemove,mousedown,mouseup,mouseover,mouseout
    //
    //
    T.component = function(config) {
        apply(this,config);

        //mix in the Observable interface
        apply(this, new T.Observable());
       
        if (config && !isDefined(config.id)) {
            this.id = "tigua-component-"+idCount++;
        }
        if (!isDefined(this.x)) this.x=0;
        if (!isDefined(this.y)) this.y=0;
        if (!isDefined(this.width)) this.width=0;
        if (!isDefined(this.height)) this.height=0;
        if (!isDefined(this.angle)) this.angle=0;
        
        var list = this.listeners;
        if (isDefined(list) && isObject(list)) {
            for (var l in list) {
                this.addEventListener(l,list[l]);
            }
        }
    }
     
    T.component.prototype.setId = function(id) {
        this.id = id;
    }
     
    T.component.prototype.setWidth = function(width) {
        if (isNumber(width)) {
            this.width = width;
        }
    }
     
    T.component.prototype.setHeight = function(height) {
        if (isNumber(height)) {
            this.height = height;
        }
    }

    T.component.prototype.setDimension = function(width,height) {
        this.setWidth(width);
        this.setHeight(height);
    }
     
    T.component.prototype.setX = function(x) {
        if (isNumber(x)) {
            this.x = x;
        }
    }
     
    T.component.prototype.setY = function(y) {
        if (isNumber(y)) {
            this.y = y;
        }
    }
     
    T.component.prototype.setXY = function(x,y) {
        this.setX(x);
        this.setY(y);
    }
     
    T.component.prototype.setAngle = function(a) {
        if (isNumber(a)) {
            this.angle = a;
        }
    }
    
    T.component.prototype.getId = function() {
        return this.id;
    }
     
    T.component.prototype.getX = function() {
        return this.x;
    }
     
    T.component.prototype.getY = function() {
        return this.y;
    }
     
    T.component.prototype.getWidth = function() {
        return this.width;
    }
     
    T.component.prototype.getHeight = function() {
        return this.height;
    }

    T.component.prototype.getAngle = function() {
        return this.angle;
    }
    
    T.component.prototype.pointInside = function(x,y) {
        if (x >= this.x && x <= (this.x+this.width)) {
            if (y >= this.y && y <= (this.y+this.height)) {
                return true;
            }
        }

        return false;
    }

    T.component.prototype.doRender = function(ctx,canvas) {
        ctx.save();
        ctx.translate(this.x, this.y);
        
        if (this.angle != 0) ctx.rotate(this.angle);
        
        //Clip boundries?
        //ctx.beginPath();
        //ctx.moveTo(0,0);
        //ctx.lineTo(this.width,0);
        //ctx.lineTo(this.width,this.height);
        //ctx.lineTo(0,this.height);
        //ctx.closePath();
        //ctx.clip();
       
        this.render(ctx,canvas);
       
        ctx.restore();
    }
     
    T.component.prototype.render = function(ctx) {}
    
    T.component.prototype.moveTo = function(toX,toY,ms,easing,callback) {
      toX && this.parent.addTweenTask(createTweenTask(this,'x',toX,ms,easing,callback));
      toY && this.parent.addTweenTask(createTweenTask(this,'y',toY,ms,easing,callback));
      this.parent.startTweenTasks();
    }

    T.component.prototype.sizeTo = function(toWidth,toHeight,ms,easing,callback) {
      toWidth  && this.parent.addTweenTask(createTweenTask(this,'width' ,toWidth ,ms,easing,callback));
      toHeight && this.parent.addTweenTask(createTweenTask(this,'height',toHeight,ms,easing,callback));
      this.parent.startTweenTasks();
    }
    
    T.component.prototype.rotateTo = function(toAngle,ms,easing,callback) {
      toAngle  && this.parent.addTweenTask(createTweenTask(this,'angle' ,toAngle ,ms,easing,callback));
      this.parent.startTweenTasks();
    }
    
    T.component.prototype.attrTo = function(attr,toValue,ms,easing,callback) {
      toValue  && this.parent.addTweenTask(createTweenTask(this,attr,toValue,ms,easing,callback));
      this.parent.startTweenTasks();
    }

    //
    //
    //Image Component
    //
    //
    function image(config) {
        T.component.call(this,config);
        if (isDefined(this.src)) {
          var that = this;
          this.img = new Image();          
          this.img.onload = function(){  
            if (!that.width)  that.width  = that.img.width;
            if (!that.height) that.height = that.img.height;
            that.loaded = true;
            that.parent.render();
          }
          this.img.src = this.src;
          that.loaded = false;
        }
    }
    image.prototype = new T.component;
    image.prototype.render = function (ctx,canvas) {
        if (this.loaded) ctx.drawImage(this.img,0,0,this.width,this.height);
    }    
    
    T.image = image;
    
    //
    //
    //Image Slice Component
    //
    //
    function image_slice(config) {
        T.component.call(this,config);
        
        if (!isDefined(this.sx)) this.sx = 0;
        if (!isDefined(this.sy)) this.sy = 0;        
    }
    image_slice.prototype = new T.component;
    image_slice.prototype.render = function (ctx,canvas) {
        ctx.drawImage(this.img,this.sx,this.sy,this.width,this.height,0,0,this.width,this.height);
    }    
    
    T.image_slice = image_slice;
    
    //
    //
    //Text Component
    //
    //
    function text(config) {
        T.component.call(this,config);

        if (!isDefined(this.text)) this.text="";

        //if no color given default to black, if no size default to 12
        if (!isDefined(this.color)) this.color = "#0A0A0A";
        if (!isDefined(this.fontSize))  this.fontSize = 12;
        if (!isDefined(this.fontFamily))  this.fontFamily = 'sans-serif';
        if (!isDefined(this.textAlign))  this.textAlign = 'start';
        if (!this.height)  this.height = this.fontSize;
        if (isObject(this.color)) this.color = this.color.toString();
        if (isObject(this.scolor)) this.scolor = this.scolor.toString();
    }
    text.prototype = new T.component;
    text.prototype.render = function (ctx,canvas) {
        ctx.textBaseline='bottom';
        ctx.textAlign=this.textAlign;
        ctx.fillStyle = this.color;
        ctx.strokeStyle = this.scolor;
        ctx.font = this.fontSize+'px '+this.fontFamily;
        ctx.fillText(this.text,0,this.fontSize);
        if (this.stroke) ctx.strokeText(this.text,0,this.fontSize);
        if (!this.width) this.width = ctx.measureText(this.text).width;
        //ctx.strokeRect(0,0,this.width,this.height);
    }

    T.text = text;

    // animation easing formulas
    T.easing_formulas = {
        linear: function (n) {
            return n;
        },
        "<": function (n) {
            return pow(n, 3);
        },
        ">": function (n) {
            return pow(n - 1, 3) + 1;
        },
        "<>": function (n) {
            n = n * 2;
            if (n < 1) {
                return pow(n, 3) / 2;
            }
            n -= 2;
            return (pow(n, 3) + 2) / 2;
        },
        backIn: function (n) {
            var s = 1.70158;
            return n * n * ((s + 1) * n - s);
        },
        backOut: function (n) {
            n = n - 1;
            var s = 1.70158;
            return n * n * ((s + 1) * n + s) + 1;
        },
        elastic: function (n) {
            if (n == 0 || n == 1) {
                return n;
            }
            var p = .3,
            s = p / 4;
            return pow(2, -10 * n) * math.sin((n - s) * (2 * math.PI) / p) + 1;
        },
        bounce: function (n) {
            var s = 7.5625,
            p = 2.75,
            l;
            if (n < (1 / p)) {
                l = s * n * n;
            } else {
                if (n < (2 / p)) {
                    n -= (1.5 / p);
                    l = s * n * n + .75;
                } else {
                    if (n < (2.5 / p)) {
                        n -= (2.25 / p);
                        l = s * n * n + .9375;
                    } else {
                        n -= (2.625 / p);
                        l = s * n * n + .984375;
                    }
                }
            }
            return l;
        }
    };

    //
    //
    //
    //color: class for canvas color, allows you to specify color either by rgba or hsva.
    //       Allows for conversions between rgb or hsv as well.
    //
    //rgb values are from 0 to 255
    //h value is from 0 to 360
    //s,v values are from 0 to 100
    //a value is from 0 to 1
    //
    function color(init) {
        this.r = 0;
        this.g = 0;
        this.b = 0;
        this.h = 0;
        this.s = 0;
        this.v = 0;
        this.a = undefined;
       
        if (init) {
            if (isString(init) || init.hex) {
                this.setHex(init.hex ? init.hex:init);
            }
            else if (isDefined(init.r)) {
                this.setRGB(init.r, init.g, init.b);
            }
            else if (isDefined(init.h)) {
                this.setHSV(init.h, init.s, init.v);
            }
         
            if (init.a) this.a=init.a;
        }
       
        return this;
    }
     
    color.prototype.setHex = function(hex) {
        this.hex = hex;
       
        var newRgb = this.hexToRGB(this.hex);
        this.r = newRgb.r;
        this.g = newRgb.g;
        this.b = newRgb.b;
       
        var newHsv = this.rgbToHSV(this);
        this.h = newHsv.h;
        this.s = newHsv.s;
        this.v = newHsv.v;
    }
     
    color.prototype.setRGB = function (r, g, b) {
        this.r = r;
        this.g = g;
        this.b = b;
       
        var newHsv = this.rgbToHSV(this);
        this.h = newHsv.h;
        this.s = newHsv.s;
        this.v = newHsv.v;
       
        this.hex = this.rgbToHex(this);
    };
     
    color.prototype.setHSV = function(h, s, v) {
        this.h = h;
        this.s = s;
        this.v = v;
       
        var newRgb = this.hsvToRGB(this);
        this.r = newRgb.r;
        this.g = newRgb.g;
        this.b = newRgb.b;
       
        this.hex = this.rgbToHex(this);
    };
     
    color.prototype.setAlpha = function(a) {
        this.a=a;
    };
     
    color.prototype.rgbToHex = function (rgb) {
        return this.intToHex(rgb.r) + this.intToHex(rgb.g) + this.intToHex(rgb.b);
    };
     
    color.prototype.hexToInt = function (hex) {
        return(parseInt(hex,16));
    };
     
    color.prototype.intToHex = function (dec) {
        var result = (parseInt(dec).toString(16));
        if (result.length == 1)
            result = ("0" + result);
        return result.toUpperCase();
    };
     
    color.prototype.validateHex = function(hex) {
        hex = new String(hex).toUpperCase();
        hex = hex.replace(/[^A-F0-9]/g, '0');
        if (hex.length > 6) hex = hex.substring(0, 6);
        return hex;
    };
     
    color.prototype.hexToRGB = function(hex) {
        hex = this.validateHex(hex);
       
        var r='00', g='00', b='00';
       
        if (hex.length == 6) {
            r = hex.substring(0,2);
            g = hex.substring(2,4);
            b = hex.substring(4,6);
        } else {
            if (hex.length > 4) {
                r = hex.substring(4, hex.length);
                hex = hex.substring(0,4);
            }
            if (hex.length > 2) {
                g = hex.substring(2,hex.length);
                hex = hex.substring(0,2);
            }
            if (hex.length > 0) {
                b = hex.substring(0,hex.length);
            }
        }
       
        return {
            r:this.hexToInt(r),
            g:this.hexToInt(g),
            b:this.hexToInt(b)
        };
    };
     
    color.prototype.rgbToHSV = function (rgb) {
       
        var r = rgb.r / 255;
        var g = rgb.g / 255;
        var b = rgb.b / 255;
       
        hsv = {
            h:0,
            s:0,
            v:0
        };
       
        var min = 0
        var max = 0;
       
        if (r >= g && r >= b) {
            max = r;
            min = (g > b) ? b : g;
        } else if (g >= b && g >= r) {
            max = g;
            min = (r > b) ? b : r;
        } else {
            max = b;
            min = (g > r) ? r : g;
        }
       
        hsv.v = max;
        hsv.s = (max) ? ((max - min) / max) : 0;
       
        if (!hsv.s) {
            hsv.h = 0;
        } else {
            delta = max - min;
            if (r == max) {
                hsv.h = (g - b) / delta;
            } else if (g == max) {
                hsv.h = 2 + (b - r) / delta;
            } else {
                hsv.h = 4 + (r - g) / delta;
            }
         
            hsv.h = parseInt(hsv.h * 60);
            if (hsv.h < 0) {
                hsv.h += 360;
            }
        }
       
        hsv.s = parseInt(hsv.s * 100);
        hsv.v = parseInt(hsv.v * 100);
       
        return hsv;
    };
     
    color.prototype.hsvToRGB = function (hsv) {
       
        rgb = {
            r:0,
            g:0,
            b:0
        };
       
        var h = hsv.h;
        var s = hsv.s;
        var v = hsv.v;
       
        if (s == 0) {
            if (v == 0) {
                rgb.r = rgb.g = rgb.b = 0;
            } else {
                rgb.r = rgb.g = rgb.b = parseInt(v * 255 / 100);
            }
        } else {
            if (h == 360) {
                h = 0;
            }
            h /= 60;
         
            // 100 scale
            s = s/100;
            v = v/100;
         
            var i = parseInt(h);
            var f = h - i;
            var p = v * (1 - s);
            var q = v * (1 - (s * f));
            var t = v * (1 - (s * (1 - f)));
            switch (i) {
                case 0:
                    rgb.r = v;
                    rgb.g = t;
                    rgb.b = p;
                    break;
                case 1:
                    rgb.r = q;
                    rgb.g = v;
                    rgb.b = p;
                    break;
                case 2:
                    rgb.r = p;
                    rgb.g = v;
                    rgb.b = t;
                    break;
                case 3:
                    rgb.r = p;
                    rgb.g = q;
                    rgb.b = v;
                    break;
                case 4:
                    rgb.r = t;
                    rgb.g = p;
                    rgb.b = v;
                    break;
                case 5:
                    rgb.r = v;
                    rgb.g = p;
                    rgb.b = q;
                    break;
            }
         
            rgb.r = parseInt(rgb.r * 255);
            rgb.g = parseInt(rgb.g * 255);
            rgb.b = parseInt(rgb.b * 255);
        }
       
        return rgb;
    };
     
    color.prototype.webSafeDec = function (dec) {
        dec = Math.round(dec / 51);
        dec *= 51;
        return dec;
    };
     
    color.prototype.ToWebSafe =  function() {
        return new color({
            r: this.webSafeDec(this.r),
            g: this.webSafeDec(this.g),
            b: this.webSafeDec(this.b)
        });
    };
     
    color.prototype.toString = function() {
        var s;
        if (this.a != undefined) s = 'rgba(';
        else s = 'rgb(';
        s+=this.r+","+this.g+","+this.b;
        if (this.a != undefined) s+=","+this.a;
        s+=")";
        return s;
    }
     
    color.prototype.lighter = function(amt) {
        amt = amt || 10;
       
        var c = new color(this);
        c.v+=amt;
        if (c.v > 100) c.v=100;
       
        c.setHSV(c.h, c.s, c.v);
       
        return c;
    }
     
    color.prototype.darker = function(amt) {
        amt = amt || 10;
        var c = new color(this);
        c.v-=amt;
        if (c.v < 0) c.v=0;
       
        c.setHSV(c.h, c.s, c.v);
       
        return c;
    }
     
    color.prototype.saturate = function(amt) {
        amt = amt || 10;
       
        var c = new color(this);
        c.s+=amt;
        if (c.s > 100) c.s=100;
       
        c.setHSV(c.h, c.s, c.v);
       
        return c;
    }
     
    color.prototype.unsaturate = function(amt) {
        amt = amt || 10;
        var c = new color(this);
        c.s-=amt;
        if (c.s < 0) c.s=0;
       
        c.setHSV(c.h, c.s, c.v);
       
        return c;
    }
     
    color.prototype.highlight = function() {
        var highlight = new color(this);
        if (highlight.s>10) highlight.s = 10;
       
        highlight.setHSV(highlight.h,highlight.s,100);
       
        return highlight;
    }
     
    T.color = color;

    //
    //
    //
    // Widgets
    //
    //
    //

    T.widgets = {};
    W=T.widgets;

    //
    // private utility functions for widgets
    //
    function roundedRect(ctx,width,height,radius){
        ctx.beginPath();
        ctx.moveTo(0,radius);
        ctx.lineTo(0,height-radius);
        ctx.quadraticCurveTo(0,height,radius,height);
        ctx.lineTo(width-radius,height);
        ctx.quadraticCurveTo(width,height,width,height-radius);
        ctx.lineTo(width,radius);
        ctx.quadraticCurveTo(width,0,width-radius,0);
        ctx.lineTo(radius,0);
        ctx.quadraticCurveTo(0,0,0,radius);
    }

    function roundedRectTop(ctx,width,height,radius){
        ctx.beginPath();
        ctx.moveTo(0,radius);
        ctx.lineTo(0,height*0.4);
        //ctx.quadraticCurveTo(width*0.5,height*0.6,width,height*0.4);
        ctx.bezierCurveTo(width*0.25,height*0.5, width*0.75,height*0.5, width,height*0.4);
        ctx.lineTo(width,radius);
        ctx.quadraticCurveTo(width,0,width-radius,0);
        ctx.lineTo(radius,0);
        ctx.quadraticCurveTo(0,0,0,radius);
    }

    function roundedRectBottom(ctx,width,height,radius){
        ctx.beginPath();
        ctx.moveTo(0,height*0.6);
        ctx.lineTo(0,height-radius);
        ctx.quadraticCurveTo(0,height,radius,height);
        ctx.lineTo(width-radius,height);
        ctx.quadraticCurveTo(width,height,width,height-radius);
        ctx.lineTo(width,height*0.6);
        ctx.bezierCurveTo(width*0.25,height*0.5, width*0.75,height*0.5, 0,height*0.6);
    }

    function drawButton(ctx,width,height,color,top) {
        ctx.save();

        //ctx.translate(x, y);

        //do lower part without gradient
        roundedRect(ctx,width,height,width/10.0);

        ctx.fillStyle = color.toString();
        ctx.shadowOffsetX = 0;
        ctx.shadowOffsetY = 4;//width/50;
        ctx.shadowBlur = 2;
        ctx.shadowColor = "rgba(0, 0, 0, 0.5)";

        ctx.fill();

        //Do upper part with gradient
        ctx.shadowOffsetX = 0;
        ctx.shadowOffsetY = 0;
        ctx.shadowBlur = 0;
        ctx.shadowColor = "rgba(0, 0, 0, 0)";

        var highlight = color.highlight();
        var lineargradient = ctx.createLinearGradient(width/2,0,width/2,height);

        if (!top) {
            roundedRectBottom(ctx,width,height,width/10.0);
            lineargradient.addColorStop(0.50,color.toString());
            lineargradient.addColorStop(1,highlight); //"rgba(255,255,255,1)");
        } else {
            roundedRectTop(ctx,width,height,width/10.0);
            lineargradient.addColorStop(0,highlight); //"rgba(255,255,255,1)");
            lineargradient.addColorStop(0.50,color.toString());
        }
        
        ctx.fillStyle = lineargradient;

        //ctx.globalAlpha = 0.20;
        ctx.fill();

        ctx.restore();

    }

    function centerText(ctx,text,size,color,x,y,width,height) {
        ctx.save();

        ctx.beginPath();
        ctx.lineWidth = 1;
        ctx.textBaseline='bottom';
        ctx.textAlign='center';
        ctx.fillStyle = color;
        ctx.strokeStyle = color;//"rgba(100,100,100,1)";
        ctx.font = size+'px sans-serif';
        x += width/2;
        y +=size/2 + height/2;
        ctx.fillText(text,x,y);
        ctx.strokeText(text,x,y);

        ctx.restore();
    }

    function button(config) {
        T.component.call(this,config);

        if (!isDefined(this.width)) this.width=50;
        if (!isDefined(this.height)) this.height=30;

        //if no color given default to red
        if (!isDefined(this.color)) this.color = new tigua.color({
            h:0,
            s:70,
            v:70
        });

        this.orig_color     = this.color;
        this.hover_color    = this.color.lighter().unsaturate(30);
        this.disabled_color = this.color.unsaturate(100);
        this.top = true;

        this.addEventListener('mouseover',function(e) {
            this.color = this.hover_color;
        });
        this.addEventListener('mouseout',function(e) {
            this.color = this.orig_color;
        });
        this.addEventListener('mousedown',function(e) {
            //this.color = this.disabled_color;
            this.top=false;
        });
        this.addEventListener('mouseup',function(e) {
            //this.color = this.orig_color;
            this.top=true;
        });
    }
    button.prototype = new T.component;
    button.prototype.setDisabled = function (arg) {
      this.disabled = arg;
    }
    button.prototype.isDisabled = function (a) {
      return this.disabled == true;
    }
    button.prototype.render = function (ctx,canvas) {
        drawButton(ctx,this.width,this.height,
        this.disabled ? this.disabled_color:this.color,
        this.top)

        if (this.text) {
            var c = "#FFFFFF";
            if (this.textColor) {
                c = this.textColor;
            }
            centerText(ctx,this.text,12,c,0,0,this.width,this.height);
        }
    }
    W.button = button;
    
    return T;
})();
